import datetime
import hashlib
import re
import socket
import string
from datetime import datetime, timedelta
from typing import Callable, Iterator

import bcrypt
from dateutil.relativedelta import relativedelta

from app.libs.time_helper import str_to_timestamp, to_datetime_str


class GetSetTer(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        print("getter of x called")
        return self._x

    @x.setter
    def x(self, value):
        print("setter of x called")
        self._x = value

    @x.deleter
    def x(self):
        print("deleter of x called")
        del self._x


def get_host():
    s = None
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.connect(('114.114.114.114', 80))
        ip = s.getsockname()[0]
    finally:
        s.close()
    return ip


def verify_phone(phone):
    """校验是否手机号码"""
    return bool(re.match(re.compile(r'^1[3-9]\d{9}$'), phone))


def add_param_if_true(params, key, value, only_check_none=False):
    """
    不为空则添加到参数中
    :param params 要加入元素的字典
    :param key 要加入字典的key值
    :param value 要加入字典的value值
    :param only_check_none 是否只检查空值 [0、false等是有意义的]
    """
    if value or (only_check_none and value is not None):
        params[key] = value


def get_params_str(params: dict, sort: bool = False) -> str:
    """
    用于拼接 param query string
    :param params: 字典参数
    :param sort: 是否需要对字典进行按键升序排序, 这通常用于加密相关
    :return: k1=v1&k2=v2
    """
    if sort:
        return "&".join([f"{key}={params[key]}" for key in sorted(params.keys())])
    else:
        return "&".join([f"{key}={value}" for key, value in params.items()])


def get_md5_str(sign_dict: dict, salt: str = "", sort: bool = True) -> str:
    """
    根据盐加密对应的字典, 返回对应的md5字符串
    参与加密的参数字典默认会按照键升序排序
    :param sign_dict: 加密字典参数
    :param salt: 加密盐, 默认为空串
    :param sort: 通常需要对字典进行按键升序排序, 默认为真
    :return: 32位的md5加密字符串
    """
    sign_str = get_params_str(sign_dict, sort=sort)
    md5_obj = hashlib.md5()
    md5_obj.update((sign_str + salt).encode())
    return md5_obj.hexdigest()


def handle_datetime(_obj):
    """
    正则匹配时间，获取format_str
    2020-11-20 15:49:38.000
    """
    try:
        # 兼容db_server
        if isinstance(_obj, str):
            _obj = _obj.replace("T", " ")
            return to_datetime_str(str_to_timestamp(_obj))
        elif isinstance(_obj, int):
            return _obj
        elif isinstance(_obj, datetime):
            return to_datetime_str(_obj.timestamp())
        elif _obj is None:
            return
        else:
            raise Exception(f"意外的时间格式: {_obj},{type(_obj)}")
    except:
        raise Exception(f"意外的时间格式: {_obj},{type(_obj)}")


def format_response(_obj, _key=None):
    _new_obj = None
    if isinstance(_obj, dict):
        _new_obj = {}
        for k, v in _obj.items():
            key, info = format_response(v, k)
            _new_obj[key] = info

    elif isinstance(_obj, list):
        _new_obj = []
        for i in _obj:
            _, info = format_response(i)
            _new_obj.append(info)
    elif _key and _key.endswith("_at"):
        _new_obj = handle_datetime(_obj)
        _key = f"{_key[:-3]}_at"
    else:
        _new_obj = _obj
    return _key, _new_obj


def set_response(content, do_format=True):
    """
    在model检查前处理数据
    用于统一响应数据
    此时拿到的数据已经是未经过model检查的
    """
    content["now_time"] = int(time.time())
    if content.get("data") and do_format:
        _, content["data"] = format_response(content["data"])
    return content


def format_success_response(data=None, do_format=True):
    """成功的响应"""
    data = data or {}
    content = {
        'code': 200,
        'message': 'SUCCESS',
        'data': data
    }
    return set_response(content, do_format)


def format_fail_response(message, data=None, code=500, do_format=True):
    """失败的响应【格式化时间：将 _time 结尾的字段全部转化为 _ts】"""
    if data is None:
        data = {}
    content = {
        'code': code,
        'message': str(message),
        'data': data
    }
    return set_response(content, do_format)


def success_response(data=None, do_format=False):
    """成功的响应"""
    return format_success_response(data, do_format=do_format)


def fail_response(message, data=None, code=500):
    """失败的响应【不格式化时间】"""
    return format_fail_response(message, data, code, do_format=False)


def _set_column_data(worksheet, data_list, header_dict):
    """设置表格的内容"""
    column_widths = {}
    for i, (field, name) in enumerate(header_dict.items()):
        worksheet.write(0, i, name)
        column_widths[i] = max(column_widths.get(i, 0), len(str(name)))

    for i, field in enumerate(header_dict.keys()):
        for j, value in enumerate(data_list[field], start=1):
            worksheet.write(j, i, value)
            column_widths[i] = max(column_widths.get(i, 0), len(str(value)))

    for col_num, width in column_widths.items():
        worksheet.set_column(col_num, col_num, width)


# def generate_trade_no(length=32) -> str:
#     """生成订单号"""
#     # 获取当前时间，格式化为 %Y%m%d%H%M%S
#     current_time = datetime.now().strftime('%Y%m%d%H%M%S')
#     # 生成 UUID，并去掉短横线
#     unique_id = str(uuid.uuid4()).replace('-', '')
#     # 拼接前缀和唯一标识符，返回订单号
#     trade_no = current_time + unique_id
#     return trade_no[:length]


def url_params_to_dict(url_params) -> dict:
    """url参数转换成字典格式"""
    return dict(item.split('=') for item in url_params.split('&'))


def groupby(iterable: Iterator, keyfunc: Callable):
    """
    按照keyfunc返回的值分组
    :param iterable:
    :param keyfunc:
    :return:
    """
    ret = {}

    if not iterable:
        return {}

    if not keyfunc:
        keyfunc = lambda x: x

    for item in iterable:
        key = keyfunc(item)
        ret.setdefault(key, []).append(item)

    return ret


def hash_password(password):
    """ 对密码进行哈希 """
    hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
    return hashed.decode()


def check_password(hashed_password, user_password):
    """ 检查用户提供的密码是否与哈希密码匹配 """
    return bcrypt.checkpw(user_password.encode(), hashed_password.encode())


def list_obj_to_list_dict(source_list):
    """列表对象转列表字典"""
    return [_obj.to_dict() for _obj in source_list]


def check_datetime_curr_week(created_at: datetime):
    """检查时间是否在本周内"""
    now = datetime.now(tz=created_at.tzinfo)

    # 计算当前日期是周几（0是周一，6是周日）
    today_weekday = now.weekday()

    # 计算本周一的日期
    this_monday = now - timedelta(days=today_weekday)

    # 获取本周一的开始时间（即午夜12点）
    this_monday_start = this_monday.replace(hour=0, minute=0, second=0, microsecond=0)

    # 计算本周日的日期（下一个周一的前一天）
    this_sunday = this_monday + timedelta(days=6)

    # 获取本周日的结束时间（即午夜11:59:59 PM）
    this_sunday_end = this_sunday.replace(hour=23, minute=59, second=59, microsecond=999999)

    # 检查created_at是否在本周内
    if this_monday_start <= created_at <= this_sunday_end:
        return True
    else:
        return False


def get_current_week_range(date_time: str = None):
    """获取本周时间范围"""
    if date_time:
        datetime_obj = datetime.strptime(date_time[:10], '%Y-%m-%d')
    else:
        datetime_obj = datetime.today()

    start_of_week = datetime_obj - timedelta(days=datetime_obj.weekday())
    end_of_week = start_of_week + timedelta(days=6)

    start_of_week_str = start_of_week.strftime('%Y-%m-%d')
    end_of_week_str = end_of_week.strftime('%Y-%m-%d')

    return start_of_week_str, end_of_week_str


def get_current_month_range(date_time: str = None):
    """获取本月时间范围"""
    if date_time:
        datetime_obj = datetime.strptime(date_time[:7], '%Y-%m')
    else:
        datetime_obj = datetime.today()
    start_of_month = datetime(datetime_obj.year, datetime_obj.month, 1)
    end_of_month = start_of_month + relativedelta(months=1) - timedelta(days=1)

    start_of_month_str = start_of_month.strftime('%Y-%m-%d')
    end_of_month_str = end_of_month.strftime('%Y-%m-%d')

    return start_of_month_str, end_of_month_str


def mask_string(string: str):
    """隐藏字符串中间部分字符"""
    # 判断字符串长度是否小于等于8，如果是，直接返回原字符串
    if len(string) <= 8:
        return string

    # 获取前4个字符和后4个字符
    start = string[:4]
    end = string[-4:]

    # 计算中间部分星号的数量
    num_stars = len(string) - 8

    # 组合结果字符串
    return start + '*' * num_stars + end


def is_vaild_date(date_str: str, date_format: str = '%Y-%m-%d'):
    """判断字符串是否指定的日期格式"""
    try:
        datetime.strptime(date_str, date_format)
        return True
    except ValueError:
        return False


def generate_invite_code(unique_str: str):
    """根据字符串生成邀请码"""
    # 生成 MD5 哈希值
    hash_object = hashlib.md5(unique_str.encode())
    hash_hex = hash_object.hexdigest()

    # 用哈希值的前 8 个字符生成一个基础字符串
    base_string = hash_hex[:8]

    # 定义数字和大写字母的字符集
    charset = string.ascii_uppercase + string.digits

    # 使用字符集从哈希值中生成 6 位邀请码
    invite_code = ''.join([charset[int(base_string[i % len(base_string)], 16) % len(charset)] for i in range(6)])

    return invite_code


import time
import random


def generate_trade_no(prefix: str, length: int = 32):
    """
    生成指定长度的订单号
    :param prefix: 订单号前缀
    :param length: 订单号总长度
    :return: 生成的订单号
    """
    # 获取当前时间戳（年月日时分秒毫秒）
    timestamp = time.strftime("%Y%m%d%H%M%S", time.localtime()) + str(int(time.time() * 1000) % 1000).zfill(3)

    # 计算时间戳的长度
    timestamp_len = len(timestamp)

    # 判断是否长度足够
    if length < timestamp_len + len(prefix):
        raise ValueError(f"总长度(length)不能小于前缀和时间戳的长度：{len(prefix) + timestamp_len}")

    # 计算需要的随机部分长度
    random_part_len = length - len(prefix) - timestamp_len

    # 生成随机部分
    random_part = ''.join(random.choices('0123456789', k=random_part_len))

    # 组合前缀、时间戳和随机部分
    trade_no = prefix + timestamp + random_part

    return trade_no
